package com.neo.tiny.bpm.service.process.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.neo.tiny.bpm.dto.definition.BpmProcessDefinitionCreateReqDTO;
import com.neo.tiny.bpm.entity.BpmFormDO;
import com.neo.tiny.bpm.entity.BpmProcessDefinitionExtDO;
import com.neo.tiny.bpm.mapper.BpmProcessDefinitionExtMapper;
import com.neo.tiny.bpm.service.process.BpmFormService;
import com.neo.tiny.bpm.service.process.BpmProcessDefinitionService;
import com.neo.tiny.bpm.vo.process.BpmProcessDefinitionListReqVO;
import com.neo.tiny.bpm.vo.process.BpmProcessDefinitionPageItemRespVO;
import com.neo.tiny.bpm.vo.process.BpmProcessDefinitionPageReqVO;
import com.neo.tiny.bpm.vo.process.BpmProcessDefinitionRespVO;
import com.neo.tiny.common.constant.ErrorCodeConstants;
import com.neo.tiny.common.exception.WebApiException;
import com.neo.tiny.flowable.core.util.FlowableUtils;
import com.neo.tiny.query.LambdaQueryWrapperBase;
import com.neo.tiny.util.PageUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.util.Collections.emptyList;

/**
 * @Description:
 * @Author: yqz
 * @CreateDate: 2022/9/26 02:03
 */
@Slf4j
@Service
@AllArgsConstructor
public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService {
    private static final String BPMN_FILE_SUFFIX = ".bpmn";

    private final RepositoryService repositoryService;

    private final BpmProcessDefinitionExtMapper processDefinitionExtMapper;

    private final BpmFormService formService;

    @Override
    public List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds) {

        if (CollUtil.isEmpty(deploymentIds)) {
            return emptyList();
        }
        return repositoryService.createProcessDefinitionQuery().deploymentIds(deploymentIds).list();
    }

    @Override
    public ProcessDefinition getProcessDefinition2(String id) {
        return repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
    }

    @Override
    public List<Deployment> getDeployments(Set<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return emptyList();
        }
        List<Deployment> list = new ArrayList<>(ids.size());
        for (String id : ids) {
            CollUtil.addAll(list, getDeployment(id));
        }
        return list;
    }

    @Override
    public Deployment getDeployment(String id) {
        if (StrUtil.isBlank(id)) {
            return null;
        }
        return repositoryService.createDeploymentQuery()
                .deploymentId(id).singleResult();
    }

    @Override
    public BpmnModel getBpmnModel(String processDefinitionId) {
        return repositoryService.getBpmnModel(processDefinitionId);
    }

    @Override
    public boolean isProcessDefinitionEquals(BpmProcessDefinitionCreateReqDTO createReqDTO) {
        // 校验 name、description 是否更新
        ProcessDefinition oldProcessDefinition = getActiveProcessDefinition(createReqDTO.getKey());
        if (oldProcessDefinition == null) {
            return false;
        }
        BpmProcessDefinitionExtDO oldProcessDefinitionExt = getProcessDefinitionExt(oldProcessDefinition.getId());
        if (!StrUtil.equals(createReqDTO.getName(), oldProcessDefinition.getName())
                || !StrUtil.equals(createReqDTO.getDescription(), oldProcessDefinitionExt.getDescription())
                || !StrUtil.equals(createReqDTO.getCategory(), oldProcessDefinition.getCategory())) {
            return false;
        }
        // 校验 form 信息是否更新
        if (!ObjectUtil.equal(createReqDTO.getFormType(), oldProcessDefinitionExt.getFormType())
                || !ObjectUtil.equal(createReqDTO.getFormId(), oldProcessDefinitionExt.getFormId())
                || !ObjectUtil.equal(createReqDTO.getFormConf(), oldProcessDefinitionExt.getFormConf())
                || !ObjectUtil.equal(createReqDTO.getFormFields(), oldProcessDefinitionExt.getFormFields())
                || !ObjectUtil.equal(createReqDTO.getFormCustomCreatePath(), oldProcessDefinitionExt.getFormCustomCreatePath())
                || !ObjectUtil.equal(createReqDTO.getFormCustomViewPath(), oldProcessDefinitionExt.getFormCustomViewPath())) {
            return false;
        }
        // 校验 BPMN XML 信息
        BpmnModel newModel = buildBpmnModel(createReqDTO.getBpmnBytes());
        BpmnModel oldModel = getBpmnModel(oldProcessDefinition.getId());
        //TODO  貌似 flowable 不修改这个也不同。需要看看。 sourceSystemId 不同
        if (FlowableUtils.equals(oldModel, newModel)) {
            return false;
        }
        // 最终发现都一致，则返回 true
        return true;
    }

    @Override
    public ProcessDefinition getActiveProcessDefinition(String key) {
        return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult();
    }

    @Override
    public BpmProcessDefinitionExtDO getProcessDefinitionExt(String id) {
        return processDefinitionExtMapper.selectOne(new LambdaQueryWrapperBase<BpmProcessDefinitionExtDO>()
                .eq(BpmProcessDefinitionExtDO::getProcessDefinitionId, id));
    }


    @Override
    public ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId) {
        if (StrUtil.isEmpty(deploymentId)) {
            return null;
        }
        return repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploymentId).singleResult();
    }

    @Override
    public String createProcessDefinition(BpmProcessDefinitionCreateReqDTO createReqDTO) {
        // 创建 Deployment
        Deployment deploy = repositoryService.createDeployment()
                .key(createReqDTO.getKey())
                .name(createReqDTO.getName())
                .category(createReqDTO.getCategory())
                .addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
                .deploy();

        // 设置 ProcessDefinition 的 category 分类
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        repositoryService.setProcessDefinitionCategory(definition.getId(), createReqDTO.getCategory());
        // 注意 1，ProcessDefinition 的 key 和 name 是通过 BPMN 中的 <bpmn2:process /> 的 id 和 name 决定
        // 注意 2，目前该项目的设计上，需要保证 Model、Deployment、ProcessDefinition 使用相同的 key，保证关联性。
        // 否则，会导致 ProcessDefinition 的分页无法查询到。
        if (!Objects.equals(definition.getKey(), createReqDTO.getKey())) {
            throw new WebApiException(StrFormatter.format(ErrorCodeConstants.PROCESS_DEFINITION_KEY_NOT_MATCH.getMsg(), createReqDTO.getKey(), definition.getKey()));
        }
        if (!Objects.equals(definition.getName(), createReqDTO.getName())) {
            throw new WebApiException(StrFormatter.format(ErrorCodeConstants.PROCESS_DEFINITION_NAME_NOT_MATCH.getMsg(), createReqDTO.getName(), definition.getName()));
        }
        // 插入拓展表
        BpmProcessDefinitionExtDO definitionExtDO = BeanUtil.copyProperties(createReqDTO, BpmProcessDefinitionExtDO.class);
        definitionExtDO.setProcessDefinitionId(definition.getId());
        processDefinitionExtMapper.insert(definitionExtDO);
        return definition.getId();
    }

    @Override
    public void updateProcessDefinitionState(String id, Integer state) {
        // 激活
        if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
            repositoryService.activateProcessDefinitionById(id, false, null);
            return;
        }
        // 挂起
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
            // suspendProcessInstances = false，进行中的任务，不进行挂起。
            // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
            repositoryService.suspendProcessDefinitionById(id, false, null);
            return;
        }
        log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
    }

    @Override
    public ProcessDefinition getProcessDefinition(String definitionId) {
        return repositoryService.getProcessDefinition(definitionId);
    }

    @Override
    public Page<BpmProcessDefinitionPageItemRespVO> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageReqVO) {

        ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StrUtil.isNotBlank(pageReqVO.getKey())) {
            definitionQuery.processDefinitionKey(pageReqVO.getKey());
        }
        // 执行查询
        List<ProcessDefinition> processDefinitions = definitionQuery.orderByProcessDefinitionVersion().desc()
                .listPage(PageUtils.getStart(pageReqVO), pageReqVO.getSize());
        if (CollUtil.isEmpty(processDefinitions)) {
            return new Page<>();
        }
        // 获得 Deployment Map
        Set<String> deploymentIds = new HashSet<>();
        processDefinitions.forEach(processDefinition -> {
            if (StrUtil.isNotBlank(processDefinition.getDeploymentId())) {
                deploymentIds.add(processDefinition.getDeploymentId());
            }
        });
        Map<String, Deployment> deploymentMap = getDeploymentMap(deploymentIds);
        // 获得 BpmProcessDefinitionExtDO Map
        Set<String> processDefinitionIds = processDefinitions.stream()
                .map(ProcessDefinition::getId).filter(Objects::nonNull).collect(Collectors.toSet());

        List<BpmProcessDefinitionExtDO> processDefinitionExts = processDefinitionExtMapper.selectList(new LambdaQueryWrapperBase<BpmProcessDefinitionExtDO>()
                .in(BpmProcessDefinitionExtDO::getProcessDefinitionId, processDefinitionIds));
        Map<String, BpmProcessDefinitionExtDO> processDefinitionExtMap = processDefinitionExts.stream()
                .collect(Collectors.toMap(BpmProcessDefinitionExtDO::getProcessDefinitionId, Function.identity(), (key1, key2) -> key1));

        // 获得 Form Map
        Set<Long> formIds = processDefinitionExts.stream()
                .map(BpmProcessDefinitionExtDO::getFormId).collect(Collectors.toSet());

        Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);

        // 拼接结果
        long count = definitionQuery.count();
        List<BpmProcessDefinitionPageItemRespVO> respVOList = convertProcessDefinition(processDefinitions, deploymentMap, processDefinitionExtMap, formMap);

        Page<BpmProcessDefinitionPageItemRespVO> page = new Page<>();
        page.setRecords(respVOList);
        page.setTotal(count);
        page.setSize(pageReqVO.getSize());
        page.setCurrent(pageReqVO.getCurrent());
        page.setPages(count / pageReqVO.getSize() + 1);

        return page;
    }

    private List<BpmProcessDefinitionPageItemRespVO> convertProcessDefinition(List<ProcessDefinition> definitions, Map<String, Deployment> deploymentMap,
                                                                              Map<String, BpmProcessDefinitionExtDO> extMap, Map<Long, BpmFormDO> formMap) {

        return definitions.stream().map(definition -> {

            Deployment deployment = Optional.ofNullable(definition.getDeploymentId())
                    .map(deploymentId -> deploymentMap.get(definition.getDeploymentId()))
                    .orElse(null);
            BpmProcessDefinitionExtDO extDO = extMap.get(definition.getId());
            BpmFormDO form = Optional.ofNullable(extDO)
                    .map(extData -> formMap.get(extData.getFormId())).orElse(null);

            // 转换数据到vo
            BpmProcessDefinitionPageItemRespVO vo = BeanUtil.copyProperties(definition, BpmProcessDefinitionPageItemRespVO.class);

            vo.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
            Optional.ofNullable(deployment).map(Deployment::getDeploymentTime).ifPresent(vo::setDeploymentTime);
            Optional.ofNullable(form).map(BpmFormDO::getName).ifPresent(vo::setFormName);

            BeanUtil.copyProperties(extDO, vo, "id");

            return vo;
        }).collect(Collectors.toList());

    }

    @Override
    public String getProcessDefinitionBpmnXml(String processDefinitionId) {

        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        if (bpmnModel == null) {
            return null;
        }
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return StrUtil.utf8Str(converter.convertToXML(bpmnModel));
    }

    @Override
    public List<BpmProcessDefinitionRespVO> getProcessDefinitionList(BpmProcessDefinitionListReqVO listReqVO) {
        // 拼接查询条件
        ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), listReqVO.getSuspensionState())) {
            definitionQuery.suspended();
        } else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), listReqVO.getSuspensionState())) {
            definitionQuery.active();
        }
        // 执行查询
        List<ProcessDefinition> list = definitionQuery.list();
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // 获得 BpmProcessDefinitionExtDO Map
        List<String> processDefinitionIds = list.stream().map(ProcessDefinition::getId).collect(Collectors.toList());

        List<BpmProcessDefinitionExtDO> processDefinitionExtDOS = processDefinitionExtMapper.selectList(new LambdaQueryWrapperBase<BpmProcessDefinitionExtDO>()
                .in(BpmProcessDefinitionExtDO::getProcessDefinitionId, processDefinitionIds));

        Map<String, BpmProcessDefinitionExtDO> definitionMap = processDefinitionExtDOS.stream()
                .collect(Collectors.toMap(BpmProcessDefinitionExtDO::getProcessDefinitionId, Function.identity(), (key1, key2) -> key1));

        return convertDefinition(list, definitionMap);
    }

    public List<BpmProcessDefinitionRespVO> convertDefinition(List<ProcessDefinition> list,
                                                              Map<String, BpmProcessDefinitionExtDO> definitionMap) {

        return list.stream().map(definition -> {
            BpmProcessDefinitionRespVO vo = new BpmProcessDefinitionRespVO();
            BeanUtil.copyProperties(definition, vo);
            if (definition.isSuspended()) {
                vo.setSuspensionState(SuspensionState.SUSPENDED.getStateCode());
            } else {
                vo.setSuspensionState(SuspensionState.ACTIVE.getStateCode());
            }
            BpmProcessDefinitionExtDO definitionExtDO = definitionMap.get(definition.getId());
            Optional.ofNullable(definitionExtDO).ifPresent(ext -> {
                BeanUtil.copyProperties(ext, vo,"id");
            });
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 构建对应的 BPMN Model
     *
     * @param bpmnBytes 原始的 BPMN XML 字节数组
     * @return BPMN Model
     */
    private BpmnModel buildBpmnModel(byte[] bpmnBytes) {
        // 转换成 BpmnModel 对象
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes),
                true, true);
    }
}
